/*
 * ByteBufferFactory.java    0.0.1    17/set/2009
 * 
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */ 
package org.mentalsmash.tazio.net.cs.server.impl;

import java.nio.ByteBuffer;

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Factory class: used to allocate direct buffers of the size specified with the <code>capacity</code>
 * property
 * 
 * @version    0.0.1    17/set/2009    
 * @author     Andrea Reale <andrea.reale@gmail.com>
 */
class ByteBufferFactory extends BasePoolableObjectFactory {

	private final Logger log = LoggerFactory.getLogger(this.getClass()); // SL4J Logger
	
	public final static int DEFAULT_CAPACITY = 1024; // 1 Kbyte
	public final static int DEFAULT_MIN_CAPACITY = 1024; // 1 Kbyte
	public final static int DEFAULT_MAX_CAPACITY = 10*1024*1024; // 10 MByte
	
	private  int minCapacity = DEFAULT_MIN_CAPACITY;
	private  int maxCapacity = DEFAULT_MAX_CAPACITY;
	
	private int capacity; // Capacity of buffers created by this factory
	
	/**
	 * Creates a ByteBufferFactory that will allocate buffer having capacity
	 * <code>ByteBufferFactory.DEFAULT_CAPACITY</code>. The capacity setting can be changed
	 * runtime.
	 */
	public ByteBufferFactory() {
		setBufferCapacity(ByteBufferFactory.DEFAULT_CAPACITY);
	}
	
	/**
	 * Creates a ByteBufferFactory that will allocate buffers having the given
	 * <code>bufferCapacity</code> as capacity. This value <strong>must</strong> be in the range
	 * [ <code>ByteBufferFactory.getMinCapacity()</code> ,
	 * <code>ByteBufferFactory.getMaxCapacity()</code> ].
	 * @param bufferCapacity
	 * @throws IllegaArgumentException if the capacity range is violated
	 */
	public ByteBufferFactory(int bufferCapacity) {
		
		//checking for capacity limits
		if(!isCapacityValid(bufferCapacity)) {
			throw new IllegalArgumentException("Buffer size has to be between " 
					+ this.minCapacity + " and " + this.maxCapacity + ".");
		}
		
		setBufferCapacity(bufferCapacity);
	}
	
	/**
	 * Checks whether <code>capacity</code> is in the range  
	 * [ <code>ByteBufferFactory.getMinCapacity()</code> , 
	 * <code>ByteBufferFactory.getMaxCapacity()</code> ].
	 * @param capacity the capacity value to check against
	 * @return true if the capacity is in the correct range
	 */
	private boolean isCapacityValid(int capacity){
		return (capacity >= this.minCapacity) && ( capacity <= this.maxCapacity);
	}
	
	/**
	 * @return the capacity of the buffers built by this factory
	 */
	public int getBufferCapacity() {
		return this.capacity;
	}
	
	/**
	 * Change the capacity of the buffers that this factory will build now on.
	 * The capacity <strong>must</strong> be in the range
	 * [ <code>ByteBufferFactory.getMinCapacity()</code> , 
	 * <code>ByteBufferFactory.getMaxCapacity()</code> ].
	 * 
	 * No exceptions will be thrown if <code>capacity</code> is out of range:
	 * the value will be simply discarded
	 * 
	 * @param capacity the new capacity to set
	 * @return true if the capacity was successfully updated. Otherwise returns false
	 */
	public boolean setBufferCapacity(int capacity) {
		if(!isCapacityValid(capacity)) {
			return false;
		}
		
		this.capacity = capacity;
		log.debug("Capacity for the buffers created by ByteBufferFactory has been " +
				"changed to " + this.capacity + " bytes.");
		
		return true;
		
	}
	
	
	
	
	
	/**
	 * @return the minimum allowed capacity for a buffer to be built
	 */
	public int getMinCapacity() {
		return minCapacity;
	}


	/**
	 * @return the maximum capacity for a buffer to be built.
	 */
	public int getMaxCapacity() {
		return maxCapacity;
	}

	// -- This methods are to be used by the MBean interface
	
	/**
	 * Set the maximum allowed capacity for a buffer built by a BufferFactory.
	 * Setting this value will not make buffers to be built with this capacity:
	 * if you want that use <code>setCapacity()</code> instead
	 * @param set the maximum capacity for a buffer to be built
	 */
	void setMaxCapacity(int maxCapacity) {
		this.maxCapacity = maxCapacity;
		
		//possibly update the current capacity
		if( getBufferCapacity() > this.maxCapacity) {
			log.debug("The maximum capacity is greater then the current capacity setting." +
					"Updating the current setting");
			setBufferCapacity(this.maxCapacity);
		}
	}
	
	/**
	 * Set the minimum allowed capacity for a buffer built by a BufferFactory.
	 * Setting this value will not make buffers to be built with this capacity:
	 * if you want that use <code>setCapacity()</code> instead
	 * @param set the minimum capacity for a buffer to be built
	 */
	void setMinCapacity(int minCapacity) {
		this.minCapacity = minCapacity;
		
		//possibly update the current capacity
		if( getBufferCapacity() < this.minCapacity) {
			log.debug("The minimum capacity is less then the current capacity setting." +
					"Updating the current setting");
			setBufferCapacity(this.minCapacity);
		}
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.pool.BasePoolableObjectFactory#makeObject()
	 */
	@Override
	public Object makeObject() throws Exception {
		ByteBuffer buff = ByteBuffer.allocateDirect(this.capacity);
		log.debug("Allocated a new direct buffer of " + this.capacity + " bytes.");
		return buff;
	}
	
	/**
	 * Uninitialize an instance to be returned to the idle object pool.
	 * @param obj the instance to be passivated
	 * @throws Exception if there is a problem passivating <code>obj</code>, this exception may
	 *                   be swallowed by the pool
	 */
	@Override
	public void passivateObject(Object obj) throws Exception {
		if (!(obj instanceof ByteBuffer)) {
			throw new IllegalArgumentException("Not an instance of a ByteBuffer");
		}
		ByteBuffer buffer = (ByteBuffer)obj;
		buffer.clear();	
	}
	


}
